---
id: tabs
title: Tabs
category: 'disclosure'
package: '@chakra-ui/tabs'
description:
  A React component that helps you build accessible tabs, by providing keyboard
  interactions and ARIA attributes described in the WAI-ARIA Tab Panel Design
  Pattern.
video: 'https://youtu.be/hSVhhOamFaI'
---

## Import

```js
import { Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react'
```

- **Tabs**: Provides context and state for all components
- **TabList**: Wrapper for the `Tab` components
- **Tab**: element that serves as a label for one of the tab panels and can be
  activated to display that panel.
- **TabPanels**: Wrapper for the `TabPanel` components
- **TabPanel**: element that contains the content associated with a tab

## Usage

You can render any element within `Tabs`, but `TabList` should only have `Tab`
as children, and `TabPanels` should have `TabPanel` as children.

Tabs expects `TabList` and `TabPanels` as children. The order doesn't matter,
you can have `TabList` at the top, at the bottom, or both.

```jsx
<Tabs>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
    <Tab>Three</Tab>
  </TabList>

  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
    <TabPanel>
      <p>three!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Tab variants and color

Tabs come in 6 different variants to style the tabs: `line`,`enclosed`,
`enclosed-colored`, `soft-rounded`, `solid-rounded`

```jsx
<Tabs variant='enclosed'>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

You can also change the color scheme for any specific variant by passing the
`colorScheme`.

> The value of `colorScheme` must exist in the theme object, and must be a key
> in `theme.colors` that has the `50` - `900` color values.

```jsx
<Tabs variant='soft-rounded' colorScheme='green'>
  <TabList>
    <Tab>Tab 1</Tab>
    <Tab>Tab 2</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Tab sizes

You can change the size of the tab by passing `size` prop. We support 3 sizes
`sm`, `md`, `lg`

```jsx
<Tabs size='md' variant='enclosed'>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Changing the tabs alignment

You can change the alignment of the `TabList` by passing `align` prop. We
support 3 sizes `start`, `center`, `end`.

```jsx
<Tabs align='end' variant='enclosed'>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Fitted Tabs

Stretch the tab list to fit the container by passing `isFitted` prop.

```jsx
<Tabs isFitted variant='enclosed'>
  <TabList mb='1em'>
    <Tab>One</Tab>
    <Tab>Two</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Styling the tab states via props

In event you need to create custom styles for the tabs. Simply set the variant
to `unstyled`, and use the `_selected`, `_hover`, `_active` style props.

```jsx
<Tabs variant='unstyled'>
  <TabList>
    <Tab _selected={{ color: 'white', bg: 'blue.500' }}>Tab 1</Tab>
    <Tab _selected={{ color: 'white', bg: 'green.400' }}>Tab 2</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Tabs onChange

The `onChange` callback returns the active tab's index whenever the user changes
tabs. If you intend to control the tabs programmatically, use this with the
`index` prop.

```jsx
function Example() {
  const colors = useColorModeValue(
    ['red.50', 'teal.50', 'blue.50'],
    ['red.900', 'teal.900', 'blue.900'],
  )
  const [tabIndex, setTabIndex] = React.useState(0)
  const bg = colors[tabIndex]
  return (
    <Tabs onChange={(index) => setTabIndex(index)} bg={bg}>
      <TabList>
        <Tab>Red</Tab>
        <Tab>Teal</Tab>
        <Tab>Blue</Tab>
      </TabList>
      <TabPanels p='2rem'>
        <TabPanel>The Primary Colors</TabPanel>
        <TabPanel>Are 1, 2, 3</TabPanel>
        <TabPanel>Red, yellow and blue.</TabPanel>
      </TabPanels>
    </Tabs>
  )
}
```

### Make a tab initially active

If you want a tab to be initially active, simply pass the `defaultIndex` prop
and set it to the index of that tab.

```jsx
<Tabs defaultIndex={1}>
  <TabPanels>
    <TabPanel>
      <Image
        boxSize='200px'
        fit='cover'
        src='https://resizing.flixster.com/wTgvsiM8vNLhCcCH-6ovV8n5z5U=/300x300/v1.bjsyMDkxMzI5O2o7MTgyMDQ7MTIwMDsxMjAwOzkwMA'
      />
    </TabPanel>
    <TabPanel>
      <Image
        boxSize='200px'
        fit='cover'
        src='https://vignette.wikia.nocookie.net/naruto/images/2/21/Sasuke_Part_1.png/revision/latest?cb=20170716092103'
      />
    </TabPanel>
  </TabPanels>
  <TabList>
    <Tab>Naruto</Tab>
    <Tab>Sasuke</Tab>
  </TabList>
</Tabs>
```

### Make a Tab disabled

When a `Tab` is disabled, it is skipped during keyboard navigation and it is not
clickable.

```jsx
function Example() {
  return (
    <Tabs>
      <TabList>
        <Tab>One</Tab>
        <Tab isDisabled>Two</Tab>
        <Tab>Three</Tab>
      </TabList>
      <TabPanels>
        <TabPanel>1</TabPanel>
        <TabPanel>2</TabPanel>
        <TabPanel>3</TabPanel>
      </TabPanels>
    </Tabs>
  )
}
```

### Tabs with manual activation

By default, `Tabs` are activated automatically. This means when you use the
arrow keys to change tabs, the tab is activated and focused.

> The content of a `TabPanel` should ideally be preloaded. However, if switching
> to a tab panel causes a network request and possibly a page refresh, there
> might be some noticeable latency and this might affect the experience for
> keyboard and screen reader users.

In this scenario, you should use a manually activated tab, it moves focus
without activating the tabs. With focus on a specific tab, users can activate a
tab by pressing <kbd>Space</kbd> or <kbd>Enter</kbd>.

```jsx
<Tabs isManual variant='enclosed'>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Lazily mounting tab panels

By default, the `Tabs` component renders all tabs content to the DOM, meaning
that invisible tabs are still rendered but are hidden by styles.

If you want to defer rendering of each tab until that tab is selected, you can
use the `isLazy` prop. This is useful if your tabs require heavy performance, or
make network calls on mount that should only happen when the component is
displayed.

```jsx
<Tabs isLazy>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
  </TabList>
  <TabPanels>
    {/* initially mounted */}
    <TabPanel>
      <p>one!</p>
    </TabPanel>
    {/* initially not mounted */}
    <TabPanel>
      <p>two!</p>
    </TabPanel>
  </TabPanels>
</Tabs>
```

### Controlled Tabs

Like form inputs, a tab's state can be controlled. Make sure to include an
onChange as well, or else the tabs will not be interactive.

```jsx
function ControlledExample() {
  const [tabIndex, setTabIndex] = React.useState(0)

  const handleSliderChange = (event) => {
    setTabIndex(parseInt(event.target.value, 10))
  }

  const handleTabsChange = (index) => {
    setTabIndex(index)
  }

  return (
    <Box>
      <input
        type='range'
        min='0'
        max='2'
        value={tabIndex}
        onChange={handleSliderChange}
      />

      <Tabs index={tabIndex} onChange={handleTabsChange}>
        <TabList>
          <Tab>One</Tab>
          <Tab>Two</Tab>
          <Tab>Three</Tab>
        </TabList>
        <TabPanels>
          <TabPanel>
            <p>Click the tabs or pull the slider around</p>
          </TabPanel>
          <TabPanel>
            <p>Yeah yeah. What's up?</p>
          </TabPanel>
          <TabPanel>
            <p>Oh, hello there.</p>
          </TabPanel>
        </TabPanels>
      </Tabs>
    </Box>
  )
}
```

### Creating custom tab components

Because `TabList` needs to know the order of the children, we use `cloneElement`
to pass state internally. Your custom `Tab` component must use
`React.forwardRef`.

```jsx
function CustomTabs() {
  const CustomTab = React.forwardRef((props, ref) => {
    // 1. Reuse the `useTab` hook
    const tabProps = useTab({ ...props, ref })
    const isSelected = !!tabProps['aria-selected']

    // 2. Hook into the Tabs `size`, `variant`, props
    const styles = useMultiStyleConfig('Tabs', tabProps)

    return (
      <Button __css={styles.tab} {...tabProps}>
        <Box as='span' mr='2'>
          {isSelected ? '😎' : '😐'}
        </Box>
        {tabProps.children}
      </Button>
    )
  })

  return (
    <Tabs>
      <TabList>
        <CustomTab>One</CustomTab>
        <CustomTab>Two</CustomTab>
      </TabList>
      <TabPanels>
        <TabPanel>1</TabPanel>
        <TabPanel>2</TabPanel>
      </TabPanels>
    </Tabs>
  )
}
```

### DataTabs

If you'd like to drive your tabs with an array instead of using the granular
components, you can create your own DataTabs component.

```jsx
function Example() {
  // 1. Create the component
  function DataTabs({ data }) {
    return (
      <Tabs>
        <TabList>
          {data.map((tab, index) => (
            <Tab key={index}>{tab.label}</Tab>
          ))}
        </TabList>
        <TabPanels>
          {data.map((tab, index) => (
            <TabPanel p={4} key={index}>
              {tab.content}
            </TabPanel>
          ))}
        </TabPanels>
      </Tabs>
    )
  }

  // 2. Create an array of data
  const tabData = [
    {
      label: 'Nigerian Jollof',
      content: 'Perhaps the greatest dish ever invented.',
    },
    {
      label: 'Pounded Yam & Egusi',
      content:
        'Perhaps the surest dish ever invented but fills the stomach more than rice.',
    },
  ]

  // 3. Pass the props and chill!
  return <DataTabs data={tabData} />
}
```

## Accessibility

### Keyboard

| Key                | Action                                                                     |
| ------------------ | -------------------------------------------------------------------------- |
| `ArrowLeft`        | Moves focus to the next tab                                                |
| `ArrowRight`       | Moves focus to the previous tab                                            |
| `Tab`              | When focus moves into the tab list, places focus on the active tab element |
| `Space` or `Enter` | Activates the tab if it was not activated automatically on focus           |
| `Home`             | Moves focus to the first tab                                               |
| `End`              | Moves focus to the last tab                                                |

### ARIA roles

| Component | Aria               | Usage                                                                      |
| --------- | ------------------ | -------------------------------------------------------------------------- |
| Tab       | `role="tab"`       | Indicates that it is a tab                                                 |
|           | `aria-selected`    | Set to `true` a tab is selected and all other Tabs have it set to `false`  |
|           | `aria-controls`    | Set to the `id` of its associated `TabPanel`                               |
| TabList   | `id`               | The `id` of the `TabPanel` that's referenced by its associated `Tab`       |
|           | `aria-orientation` | Set to vertical or horizontal based on the value of the `orientation` prop |
|           | `role="tablist"`   | Indicates that it is a tablist                                             |
| TabPanel  | `role="tabpanel"`  | Indicates that it is a tabpanel                                            |
|           | `aria-labelledby`  | Set to the `id` of the `Tab` that labels the `TabPanel`                    |

